package net.sudot.commons.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.Serializable;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * 非对称加密算法RSA算法组件
 * 非对称算法一般是用来传送对称加密算法的密钥来使用的，相对于DH算法，RSA算法只需要一方构造密钥，不需要
 * 大费周章的构造各自本地的密钥对了。DH算法只能算法非对称算法的底层实现。而RSA算法算法实现起来较为简单
 *
 * @author tangjialin on 2018-03-30.
 */
public class RSACoder {
    private static final Logger logger = LoggerFactory.getLogger(RSACoder.class);

    /** 非对称密钥算法 */
    public static final String KEY_ALGORITHM = "RSA";
    /**
     * <pre>
     * 密钥长度
     * 密钥长度必须是64的倍数，在512到65536位之间
     * Use a bigger key. 1024-bit keys can only encrypt 117 bytes without some sort of padding,
     * but a 2048-bit one can handle up to 245 bytes,
     * and a 4096-bit key up to 501 bytes.
     * That could make your script handle bigger and accented names (non-ASCII characters uses two or more bytes per character).
     * </pre>
     */
    private static final int KEY_SIZE = 2048;

    private RSACoder() {
    }

    /**
     * 私钥加密
     *
     * @param data 待加密数据
     * @param key  密钥
     * @return byte[] 加密数据
     */
    public static byte[] encryptByPrivateKey(byte[] data, byte[] key) throws Exception {
        //取得私钥
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        //生成私钥
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        //数据加密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * 公钥加密
     *
     * @param data 待加密数据
     * @param key  密钥
     * @return byte[] 加密数据
     */
    public static byte[] encryptByPublicKey(byte[] data, byte[] key) {
        try {
            //实例化密钥工厂
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            //初始化公钥
            //密钥材料转换
            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
            //产生公钥
            PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
            //数据加密
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            return cipher.doFinal(data);
        } catch (NoSuchAlgorithmException e) {
            logger.error("", e);
        } catch (InvalidKeySpecException e) {
            logger.error("", e);
        } catch (NoSuchPaddingException e) {
            logger.error("", e);
        } catch (InvalidKeyException e) {
            logger.error("", e);
        } catch (IllegalBlockSizeException e) {
            logger.error("", e);
        } catch (BadPaddingException e) {
            logger.error("", e);
        }
        return null;
    }

    /**
     * 私钥解密
     *
     * @param data 待解密数据
     * @param key  密钥
     * @return byte[] 解密数据
     */
    public static byte[] decryptByPrivateKey(byte[] data, byte[] key) {
        try {
            //取得私钥
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            //生成私钥
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
            //数据解密
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return cipher.doFinal(data);
        } catch (NoSuchAlgorithmException e) {
            logger.error("", e);
        } catch (InvalidKeySpecException e) {
            logger.error("", e);
        } catch (NoSuchPaddingException e) {
            logger.error("", e);
        } catch (InvalidKeyException e) {
            logger.error("", e);
        } catch (IllegalBlockSizeException e) {
            logger.error("", e);
        } catch (BadPaddingException e) {
            logger.error("", e);
        }
        return null;
    }

    /**
     * 公钥解密
     *
     * @param data 待解密数据
     * @param key  密钥
     * @return byte[] 解密数据
     */
    public static byte[] decryptByPublicKey(byte[] data, byte[] key) {
        try {
            //实例化密钥工厂
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            //初始化公钥
            //密钥材料转换
            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
            //产生公钥
            PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
            //数据解密
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, pubKey);
            return cipher.doFinal(data);
        } catch (NoSuchAlgorithmException e) {
            logger.error("", e);
        } catch (InvalidKeySpecException e) {
            logger.error("", e);
        } catch (NoSuchPaddingException e) {
            logger.error("", e);
        } catch (InvalidKeyException e) {
            logger.error("", e);
        } catch (IllegalBlockSizeException e) {
            logger.error("", e);
        } catch (BadPaddingException e) {
            logger.error("", e);
        }
        return null;
    }

    /**
     * 初始化密钥对
     *
     * @return Map 甲方密钥的Map
     */
    public static RSAKey generatorKey() {
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM); // 实例化密钥生成器
            keyPairGenerator.initialize(KEY_SIZE);                                           // 初始化密钥生成器
            KeyPair keyPair = keyPairGenerator.generateKeyPair();                            // 生成密钥对
            RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();                     // 公钥
            RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();                 // 私钥
            return new RSAKey(publicKey, privateKey);
        } catch (NoSuchAlgorithmException e) {
            logger.error("", e);
        }
        return null;
    }

    /**
     * 字节数组转base64
     *
     * @param bytes
     * @return
     */
    public static String byteArrayToBase64(byte[] bytes) {
        return Base64Utils.encodeToString(bytes);
    }

    /**
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        // 初始化密钥并生成密钥对
        RSAKey rsaKey = RSACoder.generatorKey();
        // 公钥
        RSAPublicKey publicKey = rsaKey.getPublicKey();
        byte[] publicKeyBytes = publicKey.getEncoded();
        // 私钥
        RSAPrivateKey privateKey = rsaKey.getPrivateKey();
        byte[] privateKeyBytes = privateKey.getEncoded();

        System.out.println("公钥：\n" + RSACoder.byteArrayToBase64(publicKeyBytes));
        System.out.println("私钥：\n" + RSACoder.byteArrayToBase64(privateKeyBytes));

        System.out.println("================密钥对构造完毕,甲方将公钥公布给乙方，开始进行加密数据的传输=============");
        String str = "RSA密码交换算法";
        System.out.println("\n===========甲方向乙方发送加密数据==============");
        System.out.println("原文:" + str);
        //甲方进行数据的加密
        byte[] code1 = RSACoder.encryptByPrivateKey(str.getBytes(), privateKeyBytes);
        System.out.println("加密后的数据：" + RSACoder.byteArrayToBase64(code1));
        System.out.println("===========乙方使用甲方提供的公钥对数据进行解密==============");
        //乙方进行数据的解密
        byte[] decode1 = RSACoder.decryptByPublicKey(code1, publicKeyBytes);
        System.out.println("乙方解密后的数据：" + new String(decode1) + "\n\n");

        System.out.println("===========反向进行操作，乙方向甲方发送数据==============\n\n");

        str = "乙方向甲方发送数据RSA算法";

        System.out.println("原文:" + str);

        //乙方使用公钥对数据进行加密
        byte[] code2 = RSACoder.encryptByPublicKey(str.getBytes(), publicKeyBytes);
        System.out.println("===========乙方使用公钥对数据进行加密==============");
        System.out.println("加密后的数据：" + RSACoder.byteArrayToBase64(code2));

        System.out.println("=============乙方将数据传送给甲方======================");
        System.out.println("===========甲方使用私钥对数据进行解密==============");

        //甲方使用私钥对数据进行解密
        byte[] decode2 = RSACoder.decryptByPrivateKey(code2, privateKeyBytes);
        System.out.println("甲方解密后的数据：" + new String(decode2));
    }

    /**
     * RSA密钥对
     *
     * @author tangjialin on 2017-01-06 0006.
     */
    public static class RSAKey implements Serializable {
        /** 公钥.byte数组类型 */
        private RSAPublicKey publicKey;
        /** 私钥.byte数组类型 */
        private RSAPrivateKey privateKey;

        private RSAKey(RSAPublicKey publicKey, RSAPrivateKey privateKey) {
            this.publicKey = publicKey;
            this.privateKey = privateKey;
        }

        public RSAPublicKey getPublicKey() {
            return publicKey;
        }

        public RSAPrivateKey getPrivateKey() {
            return privateKey;
        }
    }
}